#include "ServerLogic.h"


///////////////////// PACKET STREAM //////////////////////////////

sf::Packet& operator <<(sf::Packet& Packet, const HPacket& h)
{
	return Packet << h.m_cmd_number << h.m_infos_cmd << h.m_time;
}

sf::Packet& operator >>(sf::Packet& Packet, HPacket& h)
{
	return Packet >> h.m_cmd_number >> h.m_infos_cmd >> h.m_time;
}


/////////////////////////////// LOOP /////////////////////////////

static void gLoop()
{
	GameLoop gl;
	gl.SetFps(DataBase::getInstance()->m_fps);
	gl.Initialize();
}

static void AcceptLoop()
{
	int max_clients = DataBase::getInstance()->m_maxPlayers;
	int port = DataBase::getInstance()->m_port;

	DataBase::getInstance()->m_listener.listen(port);
	DataBase::getInstance()->m_selector.add(DataBase::getInstance()->m_listener);

	std::ostringstream oss;
	oss << port;
	std::string t = "Binding port : " + oss.str() + " with max clients : ";
	oss.str("");
	oss << max_clients;
	t += oss.str();

	LogController::getInstance()->AddToLog(LogController::L_SOCKET, t);

	while (1)
	{
		if (!DataBase::getInstance()->m_alive)
			break;

		//Change State
		if (DataBase::getInstance()->getSizeClients() == max_clients && DataBase::getInstance()->getCurrentState() == GameState::WAIT_CLIENTS)
		{
			LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Changing State : START");
			DataBase::getInstance()->setState(GameState::START);


			//SEND PACKET START
			for (int it = 0; it < DataBase::getInstance()->getSizeClients(); it++)
			{
				Client *c = DataBase::getInstance()->getElementAt(it);
				SPacket *sp = new SPacket;
				
				std::ostringstream out1, out2;
				out1 << c->m_number;
				out2 << c->m_type;

				sp->s = c->m_socket;
				sp->hp.m_cmd_number = 0;
				sp->hp.m_infos_cmd = sf::String(out1.str() + "/" + out2.str());
				sp->hp.m_time = sf::String("234567");
				DataBase::getInstance()->pushStackSend(sp);
			}
		}

		//Starting GameLoop
		if (DataBase::getInstance()->getCurrentState() == GameState::START)
		{
			std::cout << "Start new game !" << std::endl;
			LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Starting Gameloop");
			DataBase::getInstance()->m_gameLoop.create(gLoop, NULL);

			LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Changing State : INGAME");
			DataBase::getInstance()->setState(GameState::INGAME);
		}

			//New Clients
		if (DataBase::getInstance()->m_selector.wait(sf::microseconds(1)))
		{
			if (DataBase::getInstance()->m_selector.isReady(DataBase::getInstance()->m_listener))
			{
				sf::TcpSocket* client = new sf::TcpSocket;

				//Accept new clients if slots is empty
				if (DataBase::getInstance()->getSizeClients() < max_clients && DataBase::getInstance()->getCurrentState() == GameState::WAIT_CLIENTS)
				{
					if (DataBase::getInstance()->m_listener.accept(*client) == sf::Socket::Done)
					{
						Client *c = new Client;

						c->m_socket = client;
						c->m_number = DataBase::getInstance()->getSizeClients() + 1;
						if (DataBase::getInstance()->m_typeGenerate)
							c->m_type = TypePlayer::MAN;
						else
							c->m_type = TypePlayer::HUNTER;
						DataBase::getInstance()->m_typeGenerate = !DataBase::getInstance()->m_typeGenerate;

						LogController::getInstance()->AddToLog(LogController::L_SOCKET, "New Client");
	
						DataBase::getInstance()->pushBackClient(c);
						DataBase::getInstance()->m_selector.add(*client);
					}
				}
			}
			else
			{
				for (int it = 0; it < DataBase::getInstance()->getSizeClients(); it++)
				{
					Client *c = DataBase::getInstance()->getElementAt(it);
					if (DataBase::getInstance()->m_selector.isReady(*c->m_socket))
					{
						sf::Packet packet;
						sf::Socket::Status st;

						st = c->m_socket->receive(packet);

						switch(st)
						{
						case sf::Socket::Status::Done:
							if (packet)
							{
								HPacket hp;
								SPacket *sp = new SPacket;

								packet >> hp;
								sp->hp = hp;
								sp->s = c->m_socket;

								//LogController::getInstance()->AddToLog(LogController::L_SOCKET, "New Packet");
								DataBase::getInstance()->pushStackRecv(sp);
							}
							break;
						case sf::Socket::Status::Disconnected:
							DataBase::getInstance()->m_selector.remove(*c->m_socket);
							c->m_socket->disconnect();
							DataBase::getInstance()->eraseClientAt(it);
							LogController::getInstance()->AddToLog(LogController::L_SOCKET, "Client disconnected");
							break;
						}
					}
				}
			}
		}
	}
}

	static void SendLoop()
	{
		while(1)
		{
			if (!DataBase::getInstance()->m_alive)
				break;
			for (int it = 0; it < DataBase::getInstance()->getSizeStackSend(); it++)
			{
				SPacket *t = DataBase::getInstance()->popStackSend();
				if (t == NULL)
					break;
				sf::Packet p;

				p << t->hp;
				
				//if (t->hp.m_cmd_number == 0)
				//std::cout << "SEND " << t->hp.m_infos_cmd.toAnsiString() << std::endl;
				t->s->send(p);
				delete t;
			}
			Sleep(10);
		}
	}



	/////////////////////////// SERVER LOGIC CLASS //////////////////////////

	ServerLogic::ServerLogic(void)
	{
		LogController::getInstance()->startSession("log.txt");
	}

	ServerLogic::~ServerLogic(void)
	{
		LogController::getInstance()->stopSession();
	}


	void ServerLogic::start()
	{
		try
		{
			std::cout << "Starting server....";
			DataBase::getInstance()->initDatabase();

			openAcceptLoop();
			openSendLoop();

			inputServer();

			closeAcceptLoop();
			closeSendLoop();
			std::cout << " done" << std::endl;
		}
		catch (ServerException &e)
		{
			m_loopAccept.cancel();
			m_loopSend.cancel();
			DataBase::getInstance()->m_gameLoop.cancel();
			exitMessageBox(e);
		}
		catch(...)
		{
		}
	}

	void ServerLogic::inputServer()
	{
		std::string bufferIn;

		std::cout << " done" << std::endl;

		while(true)
		{
			std::cout << "$> ";
			std::cin >> bufferIn;

			LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Input command server : " + bufferIn);
			if (bufferIn== "exit")
			{
				std::cout << "Shutdown server....";
				LogController::getInstance()->AddToLog(LogController::L_WARNING, "Shutdown server...");
				DataBase::getInstance()->m_alive = false;
				break;
			}
			else if (bufferIn == "start")
			{
				LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Changing State : NEW GAME");
				DataBase::getInstance()->setState(GameState::START);
			}
			else
			{
				std::cout << "Unknown command server : " << bufferIn << std::endl;
				LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Unknown command server : " + bufferIn);
			}
		}
	}

	/////////////////// OPEN LOOPS ////////////////////

	void ServerLogic::openAcceptLoop()
	{
		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Starting loop Accept");
		m_loopAccept.create(AcceptLoop, NULL);
	}

	void ServerLogic::openSendLoop()
	{
		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Starting loop send");
		m_loopSend.create((void*)(SendLoop), NULL);
	}


	////////////////////// CLOSE LOOPS //////////////////////

	void ServerLogic::closeAcceptLoop()
	{
		//Close GameLoop
		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Stopping Gameloop");
		DataBase::getInstance()->m_gameLoop.join();

		//Close Server socket
		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Closing socket server");
		DataBase::getInstance()->m_selector.clear();
		DataBase::getInstance()->m_listener.close();

		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Closing sockets clients");
		for (int it = 0; it < DataBase::getInstance()->getSizeClients(); it++)
			DataBase::getInstance()->eraseClientAt(it);
		m_loopAccept.join();
	}

	void ServerLogic::closeSendLoop()
	{
		LogController::getInstance()->AddToLog(LogController::L_NOTICE, "Stoping loop send");
		m_loopSend.join();
	}

	void ServerLogic::exitMessageBox(ServerException &e)
	{
		LogController::getInstance()->AddToLog(LogController::L_CRITICAL, e.what());
		MessageBoxA(NULL, e.what(), "Critical Error", MB_OK | MB_ICONERROR);
		exit(EXIT_FAILURE);
	}
